/*-
 * Copyright (c) 2009 Robert N. M. Watson, Jonathan Anderson
 * All rights reserved.
 *
 * WARNING: THIS IS EXPERIMENTAL SECURITY SOFTWARE THAT MUST NOT BE RELIED
 * ON IN PRODUCTION SYSTEMS.  IT WILL BREAK YOUR SOFTWARE IN NEW AND
 * UNEXPECTED WAYS.
 * 
 * This software was developed at the University of Cambridge Computer
 * Laboratory with support from a grant from Google, Inc. 
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <libelf.h>
#include <gelf.h>

#include "libcapability.h"



/** A soname in a linked list */
struct ll_lib { const char *name; struct ll_lib *next; };


/** Extract library dependencies from an ELF executable file */
struct ll_lib* lc_extract_elf_libs(int fd);

/** Extract shared library dependancies from an ELF data section */
struct ll_lib* lc_get_libs_from_elfdata(Elf_Data*, const char *strtab, size_t len);



int lc_extractlibdeps(int fd, struct lc_dependlib **libs, size_t *len)
{
	/* TODO: check format; maybe support other things */
	struct ll_lib *first, *current;
	size_t i, bytes;
	char *strhead;


	/* extract dependancy sonames */
	first = lc_extract_elf_libs(fd);
	if(!first) return -1;
	else if(first->name == (char*) 0)
	{
		/* there are no dependancies! */
		*len = 0;
		free(first);
		*libs = malloc(1);    /* clients will free(); malloc() something */

		return 0;
	}


	/* convert to an array of lc_dependlibs */
	*len = 0;
	bytes = 0;


	/* how large will this array be? */
	for(current = first; current; current = current->next)
	{
		(*len)++;
		bytes += strnlen(current->name, 256) + 2;  /* two null term.s */
	}

	*libs = malloc(bytes);


	/* put strings after the lc_dependlib array */
	strhead = ((char*) *libs) + (*len * sizeof(struct lc_dependlib));

	/* walk the list */
	for(i = 0, current = first; current; i++)
	{
		const char *name, *path_separator;
		size_t len;
		struct ll_lib *prev;

		struct lc_dependlib *l = (*libs + i);

		name = current->name;
		len = strlen(name);
		path_separator = strrchr(name, '/');

		/* is there a path/soname path_separator? */
		if(path_separator)
		{
			l->soname = strhead;
			strhead += path_separator - name + 1;
			memcpy((char*) l->soname, name, path_separator - name);
			strhead[-1] = '\0';

			l->path_hint = strhead;
			strhead += len - (path_separator - name) + 1;
			memcpy((char*) l->path_hint, path_separator,
			       len - (path_separator - name));
			strhead[-1] = '\0';
		}
		else
		{
			l->soname = strhead;
			strhead += len + 1;
			memcpy((char*) l->soname, name, len);
			strhead[-1] = '\0';

			l->path_hint = strhead;
			strhead[0] = '\0';
			strhead += 1;
		}

		prev = current;
		current = current->next;
		free(prev);
	}
	return 0;
}


struct ll_lib* lc_extract_elf_libs(int fd)
{
	Elf *elf;
	Elf_Scn *section;
	char *strtab;
	size_t strtablen;
	struct ll_lib *libs = (struct ll_lib*) 0;


	/* make sure that the ELF library's ABI version is valid */
	if(elf_version(EV_CURRENT) == EV_NONE)
	{
		errno = EPROGMISMATCH;
		return (struct ll_lib*) 0;
	}


	/* initialise ELF parser */
	elf = elf_begin(fd, ELF_C_READ, (Elf*) 0);
	if(!elf) { errno = EBADF; return (struct ll_lib*) 0; }


	/* we only accept executable ELF files */
	if(elf_kind(elf) != ELF_K_ELF)
	{
		errno = ENOEXEC;
		return (struct ll_lib*) 0;
	}


	/* find the .dynstr and .dynamic sections */
	section = (Elf_Scn*) 0;
	strtab = (char*) 0;
	strtablen = 0;

	while((section = elf_nextscn(elf, section)) != (Elf_Scn*) 0)
	{
		GElf_Shdr shdr;
		Elf_Data *data;
		Elf_Type type;


		/* get (and sanity check) the section header */
		if(!gelf_getshdr(section, &shdr))
		{
			errno = ENOEXEC;
			return (struct ll_lib*) 0;
		}


		/* we're interested in the .dynstr and .dynamic sections */
		switch(shdr.sh_type)
		{
			case SHT_STRTAB:    type = ELF_T_BYTE;    break;
			case SHT_DYNAMIC:   type = ELF_T_DYN;     break;
			default:            continue;
		}


		/* get section data, sanity check it */
		data = elf_getdata(section, (Elf_Data*) 0);
		if(!data || (data->d_type != type))
		{
			errno = ENOEXEC;
			return (struct ll_lib*) 0;
		}
	

		/* process the data */
		switch(shdr.sh_type)
		{
			case SHT_STRTAB:
				/* we've already got the string table! */
				if(strtab) continue;

				/* copy into strtab */
				strtablen = data->d_size;
				strtab = malloc(strtablen);
				memcpy(strtab, data->d_buf, data->d_size);

				break;

			case SHT_DYNAMIC:
				/* read the sonames */
				libs = lc_get_libs_from_elfdata(data,
				                                strtab, strtablen);
				break;
		}
	}

	/* clean up */
	if(strtab) free(strtab);
	elf_end(elf);

	/* we've come this far, so there are no errors... what about no depends? */
	if(!libs)
	{
		libs = malloc(sizeof(struct ll_lib));
		libs->name = (char*) 0;
		libs->next = (struct ll_lib*) 0;
	}

	return libs;
}


struct ll_lib* lc_get_libs_from_elfdata(Elf_Data *data,
                                        const char *strtab, size_t strtablen)
{
	GElf_Dyn dynamic;
	size_t i;

	struct ll_lib *first = 0;
	struct ll_lib *current = 0;

	if(!strtab || (strtablen == 0))
	{
		errno = ENOEXEC;
		return (struct ll_lib*) 0;
	}


	/* walk the list contained in the .dynamic section */
	for(i = 0; gelf_getdyn(data, i, &dynamic) == &dynamic; i++)
	{
		struct ll_lib *l;
		const char *name;

		/* we only care about DT_NEEDED bits (lib dependancies) */
		if(dynamic.d_tag != DT_NEEDED) continue;

		/* get the soname from the string table */
		name = strtab + dynamic.d_un.d_val;

		/* construct an entry in the linked list */
		l = malloc(sizeof(struct ll_lib) + strlen(name) + 1);
		l->name = ((char*) l) + sizeof(struct ll_lib);
		strcpy((char*) l->name, name);

		if(first == 0) first = l;
		else current->next= l;

		l->next = 0;
		current = l;
	}


	return first;
}

